package com.demo.common.utils.compare;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * 字段自动对比通用类，主要用于日志记录场景，不用一个字段一个字段判断值是否相等。{@link FieldCompare} 示例代码参考测试包下FieldCompareUtilsTest，FieldCompare注解使用参考FieldCompare
 *
 * @Author xinge001
 * @Date 2024/03/18 10:40
 * @Version 1.0.0
 */
public abstract class FieldCompareUtils {

    private static final String BLANK = "";
    /**
     * 字段值转换处理器，转换后的值用equals比较
     */
    private static final Map<String, Function> valueFunctionMap = new ConcurrentHashMap<>();
    /**
     * 字段展示处理器
     */
    private static final Map<String, Function<Object, String>> showFunctionMap = new ConcurrentHashMap<>();
    /**
     * 缓存
     */
    private static final Map<Class, SoftReference<List<CompareElementWrapper>>> compareElementCache = new ConcurrentHashMap<>();

    /**
     * 注册字段对比处理逻辑函数
     *
     * @param name 对比处理器name,不能重复
     */
    public static void registerValue(String name, Function function) {
        Assert.isNull(valueFunctionMap.putIfAbsent(name, function), () -> "字段对比function注册key重复->" + name);
    }

    /**
     * 注册字段展示处理逻辑函数
     *
     * @param name 展示处理器name,不能重复
     */
    public static void registerShow(String name, Function<Object, String> function) {
        Assert.isNull(showFunctionMap.putIfAbsent(name, function), () -> "字段展示function注册key重复->" + name);
    }

    /**
     * 比较 1.有自定义比较器{@link FieldCompare#valueConvert()}的先进行值转换，再使用@link Object#equals(Object)}比较 2.实现了{@link Comparable}接口的使用{@link
     * Comparable#compareTo(Object)}对比 3.其他使用{@link Object#equals(Object)}比较
     *
     * 输出 1.实现了自定义输出的{@link FieldCompare#showConvert()}使用自定义的转换器进行输出 2.时间类对比，输出进行了格式化，使用时间标准格式{@link
     * DateTimeUtils#getCommonDateOrTime(TemporalAccessor)} 3.其他使用{@link
     * Object#toString()}转换
     *
     * @param target 目标对象，被对比的对象，老对象
     * @param current 当前对象
     * @return 数据对比结果，其中一个对象为null或者两对象class不一致时，不对比直接返回无差异
     */
    public static <T> CompareResult compare(Object target, Object current) throws Exception {
        CompareResult compareResult = new CompareResult();
        //其中一个为null不比较
        if (target == null || current == null) {
            return compareResult;
        }
        //类型不一致不比较
        if (!target.getClass().equals(current.getClass())) {
            return compareResult;
        }
        //初始容量暂定字段一半
        List<String> differences = new ArrayList<>();
        Boolean specialCheck = false;
        //获取对比元素
        List<CompareElementWrapper> elementWrapperList = getCompareElement(target.getClass());
        for (CompareElementWrapper elementWrapper : elementWrapperList) {
            Field field = elementWrapper.getField();
            Object value1 = field.get(target);
            Object value2 = field.get(current);
            //都是null不比较
            if (value1 == null && value2 == null) {
                continue;
            }
            FieldCompare annotation = elementWrapper.getAnnotation();
            String valueConvert = annotation.valueConvert();
            //如果有自定义转换器,先使用自定义转换器处理数据
            if (!BLANK.equals(valueConvert)) {
                //找不到指定转换器就返回自身
                Function function = valueFunctionMap.getOrDefault(valueConvert, Function.identity());
                value1 = function.apply(value1);
                value2 = function.apply(value2);
            }
            //比较结果
            boolean fieldCompare = true;
            //其中有一个为null就不比较了，默认不相同
            if (value1 == null || value2 == null) {
                fieldCompare = false;
            }
            //进行字段值比较
            if (fieldCompare) {
                //为什么要加上class相等，因为上面经过转换以后可能class不一致，不一致用compareTo会报错
                if (value1 instanceof Comparable && value1.getClass().equals(value2.getClass())) {
                    fieldCompare = ((Comparable) value1).compareTo(value2) == 0;
                } else {
                    fieldCompare = value1.equals(value2);
                }
            }
            //有差异，记录日志
            if (!fieldCompare) {
                if (annotation.special()) {
                    specialCheck = true;
                }
                //为空只对比不记录
                if (BLANK.equals(annotation.format())) {
                    continue;
                }
                String showValue1 = getShowValue(value1, annotation);
                String showValue2 = getShowValue(value2, annotation);
                String fieldName = annotation.name();
                //没维护字段名的，默认用实体字段名
                if (BLANK.equals(fieldName)) {
                    fieldName = field.getName();
                }
                String difference = MessageFormat.format(annotation.format(), fieldName, showValue1, showValue2);
                differences.add(difference);
            }
        }
        compareResult.setSpecialCheck(specialCheck);
        //中文逗号拼接
        compareResult.setDifference(StringUtils.join(differences, "，"));
        return compareResult;
    }

    /**
     * 获取class对应的比较元素列表
     */
    private static List<CompareElementWrapper> getCompareElement(Class cls) {
        //直接get比先contains在get效率高
        SoftReference<List<CompareElementWrapper>> softReference = compareElementCache.get(cls);
        if (softReference!=null) {
            //解决gc的问题
            List<CompareElementWrapper> elementWrapperList = softReference.get();
            if(!CollectionUtils.isEmpty(elementWrapperList)) {
                return elementWrapperList;
            }
        }
        //这里不加synchronized了，因为lock的是class，可能影响其他地方使用，这里也不是很耗性能
        List<CompareElementWrapper> elementWrapperList = new ArrayList<>();
        //copy class
        Class ori=cls;
        while (!cls.equals(Object.class)) {
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                FieldCompare annotation = field.getAnnotation(FieldCompare.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    elementWrapperList.add(new CompareElementWrapper(field, annotation));
                }
            }
            //比较父类
            cls = cls.getSuperclass();
        }
        //input cache
        compareElementCache.putIfAbsent(ori, new SoftReference<>(elementWrapperList));
        //一般情况下是一样的，除非运行时动态修改了class内容，不考虑这种情况
        return elementWrapperList;
    }

    /**
     * 获取字段展示值
     */
    private static String getShowValue(Object value, FieldCompare annotation) {
        if (value == null) {
            return annotation.nullValue();
        }
        String showConvert = annotation.showConvert();
        //使用自定义转换器
        if (!BLANK.equals(showConvert)) {
            Function<Object, String> function = showFunctionMap.getOrDefault(showConvert, Object::toString);
            return function.apply(value);
        }
        //时间格式
        if (value instanceof TemporalAccessor) {
            return DateTimeUtils.getCommonDateOrTime((TemporalAccessor) value);
        }
        return value.toString();
    }

    /**
     * 字段对比结果类
     */
    @Data
    public static class CompareResult {

        /**
         * 是否有特殊字段差异，一个实体里配置了多个special，只要有一个有差异就是true
         *
         * @see FieldCompare#special()
         */
        private Boolean specialCheck = false;
        /**
         * 差异日志，不同的差异间英文逗号分隔
         *
         * @see FieldCompareUtils#compare(Object, Object)
         */
        private String difference = BLANK;
    }

    /**
     * 对比元素包装器
     */
    @Data
    @AllArgsConstructor
    private static class CompareElementWrapper {
        /**
         * 字段
         */
        private Field field;
        /**
         * 注解
         */
        private FieldCompare annotation;
    }

}
